"""
vis3d/shapes.py
A module containing classes for all of the visualization shapes and their OpenGL rendering.

Written By:
    James Thomas
    Email: jim@houseoftechnology.org
    Web: http://mission-cognition.houseoftechnology.org/

Copyright 2009-2012

This file is part of the PandaODElib distribution.

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public License
as published by the Free Software Foundation; either version 3 of
the License, or (at your option) any later version. The text of the
GNU Lesser General Public License is included with this library in
the file named LICENSE.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
"""
from os import sep
from itertools import izip

from pandac.PandaModules import Quat, Vec3, VBase3

import shapeGenerator
import pvis_main
import materials
import color

# Base class for all shapes so isinstance() can be used
class Shape(object):
    pass

class _Primitive(Shape):
    def __init__(self, **kwargs):
        # Set up the properties using passed parameters or defaults
        self.pos = kwargs.pop('pos', Vec3(0,0,0)) # This must come before x,y,z
        if 'x' in kwargs: self.x = kwargs.pop('x')
        if 'y' in kwargs: self.y = kwargs.pop('y')
        if 'z' in kwargs: self.z = kwargs.pop('z')

    @property
    def node(self):
        return self._node
        
    @property
    def pos(self):
        return self._node.getPos()
    @pos.setter
    def pos(self, value):
        self._node.setPos(value)
    
    @property
    def x(self):
        return self._node.getX()
    @x.setter
    def x(self, value):
        self._node.setX(value)
    
    @property
    def y(self):
        return self._node.getY()
    @y.setter
    def y(self, value):
        self._node.setY(value)
    
    @property
    def z(self):
        return self._node.getZ()
    @z.setter
    def z(self, value):
        self._node.setZ(value)
    
    def _load(self, Frame):
        # This can be overloaded in order to assign a model to the
        # node directly (instead of below this node)
        # In either case, _load() must be called before __init__()
        self._node = Frame.attachNewNode("ContainerNode")

class _OrientablePrimitive(_Primitive):
    def __init__(self, **kwargs):
        _Primitive.__init__(self, **kwargs)
        
        self.q = kwargs.pop('q', Quat(1,0,0,0))

        # A reminder that we are not strictly following the Visual API        
        assert 'axis' not in kwargs
        
    @property
    def q(self):
        return self._node.getQuat()
    @q.setter
    def q(self, value):
        self._node.setQuat(value)
        
    def rotate(self, q=(1,0,0,0), origin=None):
        # Create the rotation quaternion
        qr = Quat(q)
        
        # Rotate our position around the provided origin
        self.pos = qr.xform(self.pos - origin) + origin
        
        # Rotate our orientation
        self.q = pvis_main.RotateQuat(qr, self.q)
        
class _Attributes(object):
    def __init__(self, **kwargs):
        # Do the model load
        frm = kwargs.pop('frame', pvis_main.get_selected())
        if isinstance(frm, frame):
            frm = frm.node
            
        self._load(frm)

        self.visible = kwargs.pop('visible', True)
        
        # Set up the properties using passed parameters or defaults
        self.color = kwargs.pop('color', (1,1,1,1)) # This must come before red, green, blue, alpha
        if 'red' in kwargs: self.red = kwargs.pop('red')
        if 'green' in kwargs: self.green = kwargs.pop('green')
        if 'blue' in kwargs: self.blue = kwargs.pop('blue')
        if 'alpha' in kwargs: self.alpha = kwargs.pop('alpha')
        
        self.material = kwargs.pop('material', None)
        
    @property
    def color(self):
        return self._node.getColor()
    @color.setter
    def color(self, value):
        self._node.setColor(*value)
    
    @property
    def red(self):
        return self._node.getColor()[0]
    @red.setter
    def red(self, value):
        color = self._node.getColor()
        color.w = value
        self._node.setColor(color)
    
    @property
    def green(self):
        return self._node.getColor()[1]
    @green.setter
    def green(self, value):
        color = self._node.getColor()
        color.x = value
        self._node.setColor(color)
    
    @property
    def blue(self):
        return self._node.getColor()[2]
    @blue.setter
    def blue(self, value):
        color = self._node.getColor()
        color.y = value
        self._node.setColor(color)
    
    @property
    def alpha(self):
        return self._node.getColor()[3]
    @alpha.setter
    def alpha(self, value):
        color = self._node.getColor()
        color.z = value
        self._node.setColor(color)
    
    @property
    def visible(self):
        return self._visible
    @visible.setter
    def visible(self, value):
        self._visible = bool(value)
        if self._visible:
            self._node.show()
        else:
            self._node.hide()
    
    @property
    def frame(self):
        return self._node.getParent(0) # We are only ever supposed to have one parent
    @frame.setter
    def frame(self, value):
        if value is None:
            # Assign ourselves to the default parent
            self._node.reparentTo(pvis_main.get_selected())
        elif isinstance(value, frame):
            # Allow the user to pass a frame instance rather than the frame's node
            self._node.wrtReparentTo(value.node)
        else:
            self._node.wrtReparentTo(value)

    @property
    def material(self):
        return self._node.getTexture()
    @material.setter
    def material(self, value):
        try:
            # see if they passed us one of our built in texture factory functions
            value = value()
        except TypeError:
            pass
        
        if value is not None:
            self._node.setTexture(value)
            
class _Renderable(_OrientablePrimitive, _Attributes): # Primitive property functions take precedence
    def __init__(self, **kwargs):
        # Call the base classes
        _Attributes.__init__(self, **kwargs) # This must be first
        _OrientablePrimitive.__init__(self, **kwargs)

class _RenderableArray(Shape):
    def __init__(self, **kwargs):
        self._color = VBase3(1,1,1)
        self._verticies = []
        self._colors = []

        frm = kwargs.pop('frame', pvis_main.get_selected())
        self._node = frm.attachNewNode("RenderableArray")
        
        # Set up the properties using passed parameters or defaults
        if 'pos' in kwargs: self.pos = kwargs.pop('pos')
        if 'color' in kwargs: self.colors = kwargs.pop('color')
        
    @property
    def pos(self):
        # Make a copy for the user to use
        return [vertex for vertex in self._verticies]
    @pos.setter
    def pos(self, value):
        # Make a copy of the user's iterable
        self._verticies = [Vec3(vertex) for vertex in value]
        self._fixArrays(len(self._verticies))
    
    @property
    def x(self):
        return [v.x for v in self._verticies]
    @x.setter
    def x(self, value):
        self._fixArrays(len(value))
        for x, v in izip(value, self._verticies):
            v.x = x
    
    @property
    def y(self):
        return [v.y for v in self._verticies]
    @y.setter
    def y(self, value):
        self._fixArrays(len(value))
        for y, v in izip(value, self._verticies):
            v.y = y
    
    @property
    def z(self):
        return [v.z for v in self._verticies]
    @z.setter
    def z(self, value):
        self._fixArrays(len(value))
        for z, v in izip(value, self._verticies):
            v.z = z
    
    @property
    def color(self):
        return [color for color in self._colors]
    @color.setter
    def color(self, value):
        try:
            # See if the user only supplied a single color instead of a list
            self._color = VBase3(*value)
        except TypeError:
            # Make a copy of the user's iterable
            self._colors = [VBase3(color) for color in value]
            self._fixArrays(len(self._colors))
    
    @property
    def red(self):
        return [c.red for c in self._colors]
    @red.setter
    def red(self, value):
        self._fixArrays(len(value))
        for red, c in izip(value, self._colors):
            c.red = red
    
    @property
    def green(self):
        return [c.green for c in self._colors]
    @green.setter
    def green(self, value):
        self._fixArrays(len(value))
        for green, c in izip(value, self._colors):
            c.green = green
    
    @property
    def blue(self):
        return [c.blue for c in self._colors]
    @blue.setter
    def blue(self, value):
        self._fixArrays(len(value))
        for blue, c in izip(value, self._colors):
            c.blue = blue

    @property
    def frame(self):
        return self._node.getParent(0) # We are only ever supposed to have one parent
    @frame.setter
    def frame(self, value):
        if value is None:
            # Assign ourselves to the default parent
            self._node.reparentTo(pvis_main.get_selected())
        else:
            self._node.wrtReparentTo(value)
    
    def _fixArrays(self, length):
        l = len(self._verticies)
        if l < length:
            try:
                default = self._verticies[-1]
            except IndexError:
                default = (0,0,0)
            self._verticies.extend([Vec3(default) for i in range(length-l)])
        elif l > length:
            self._verticies = self._verticies[0:length]

        l = len(self._colors)
        if l < length:
            try:
                default = self._colors[-1]
            except IndexError:
                default = self._color
            self._colors.extend([VBase3(default) for i in range(length-l)])
        elif l > length:
            self._colors = self._colors[0:length]

    def _load(self, Frame):
        assert False, '_load() must be overloaded by your child class'
        
    def append(self, *args, **kwargs):
        if args:
            self._verticies.append(Vec3(args[0]))
        else:
            self._verticies.append(Vec3(kwargs.pop('pos', (0,0,0))))

        color = kwargs.pop('color', None)
        self._colors.append(VBase3(color) if color else None)


#**********************************
#**********************************
#**********************************

class frame(_Renderable):
    
    @property
    def objects(self):
        return self._node.getChildren()
    
    def add_renderable(self, child):
        child.wrtReparentTo(self._node)
        
    def remove_renderable(self, child):
        child.detachNode()
    
    def frame_to_world(self, frame_pos):
        return self.q.xform(frame_pos) + self.pos
    
    def world_to_frame(self, world_pos):
        qInv = pvis_main.QuatInverse(self.q)
        p = Vec3(world_pos) - self.pos
        return qInv.xform(p)

class box(_Renderable):
    def __init__(self, **kwargs):
        # Call the base class
        
        _Renderable.__init__(self, **kwargs)

        # Set up the properties unique to this shape -- use pop so
        # we consume the keys so the base class does not get extras
        self.size = kwargs.pop('size', (1,1,1)) # This must be before l,h,w
        if 'length' in kwargs: self.length = kwargs.pop('length') # x-axis
        if 'height' in kwargs: self.height = kwargs.pop('height') # y-axis
        if 'width' in kwargs: self.width = kwargs.pop('width') # z-axis
        
    @property
    def size(self):
        return self._node.getScale()
    @size.setter
    def size(self, value):
        self._node.setScale(value)
    
    @property
    def length(self):
        return self._node.getSx()
    @length.setter
    def length(self, value):
        self._node.setSx(value)
    
    @property
    def height(self):
        return self._node.getSy()
    @height.setter
    def height(self, value):
        self._node.setSy(value)
    
    @property
    def width(self):
        return self._node.getSz()
    @width.setter
    def width(self, value):
        self._node.setSz(value)
    
    def _load(self, Frame):
        self._node = shapeGenerator.Cube(1,1,1)

        self._node.reparentTo(Frame)

class pyramid(_Renderable):
    def __init__(self, **kwargs):
        # Call the base class
        
        _Renderable.__init__(self, **kwargs)

        # Set up the properties unique to this shape -- use pop so
        # we consume the keys so the base class does not get extras
        self.size = kwargs.pop('size', (1,1,1)) # This must be before l,h,w
        if 'length' in kwargs: self.length = kwargs.pop('length') # x-axis
        if 'height' in kwargs: self.height = kwargs.pop('height') # y-axis
        if 'width' in kwargs: self.width = kwargs.pop('width') # z-axis
        
        # If there are any keys left in kwargs then raise an error
        if kwargs:
            #raise TypeError, "Unexpected keyword argument(s): " + str(kwargs.keys())
            pass # This is not working right, super clases are getting copies of kwargs, not references
        
    @property
    def size(self):
        return self._node.getScale()
    @size.setter
    def size(self, value):
        self._node.setScale(value)
    
    @property
    def length(self):
        return self._node.getSx()
    @length.setter
    def length(self, value):
        self._node.setSx(value)
    
    @property
    def height(self):
        return self._node.getSy()
    @height.setter
    def height(self, value):
        self._node.setSy(value)
    
    @property
    def width(self):
        return self._node.getSz()
    @width.setter
    def width(self, value):
        self._node.setSz(value)
    
    def _load(self, Frame):
        self._node = shapeGenerator.Pyramid(1)
        self._node.reparentTo(Frame)

class sphere(_Renderable):
    def __init__(self, **kwargs):
        # Call the base class
        _Renderable.__init__(self, **kwargs)

        # Set up the properties unique to this shape -- use pop so
        # we consume the keys so the base class does not get extras
        self.radius = kwargs.pop('radius', 1)
        
        # If there are any keys left in kwargs then raise an error
        if kwargs:
            #raise TypeError, "Unexpected keyword argument(s): " + str(kwargs.keys())
            pass # This is not working right, super clases are getting copies of kwargs, not references
        
    @property
    def radius(self):
        return self._node.getSx()
    @radius.setter
    def radius(self, value):
        self._node.setScale(float(value))
    
    def _load(self, Frame):
        self._node = shapeGenerator.Sphere(1)
        self._node.reparentTo(Frame)
            
class cylinder(_Renderable):
    def __init__(self, **kwargs):
        # For some reason visual allows the length to be set with the axis vector
        # Attempt to duplicate this behavior by using the axis vector magnitude as the
        # default for the length parameter.
        #length = Vec3(kwargs.get('axis', Vec3(1,0,0))).mag

        # Call the base class
        _Renderable.__init__(self, **kwargs)
        
        # Set up the properties unique to this shape -- use pop so
        # we consume the keys so the base class does not get extras
        self.radius = kwargs.pop('radius', 1)
        
        #self.length = kwargs.pop('length', length)
        self.length = kwargs.pop('length', 1)

        # If there are any keys left in kwargs then raise an error
        if kwargs:
            #raise TypeError, "Unexpected keyword argument(s): " + str(kwargs.keys())
            pass # This is not working right, super clases are getting copies of kwargs, not references
        
    @property
    def radius(self):
        return self._node.getSx()
    @radius.setter
    def radius(self, value):
        self._node.setSx(value)
        self._node.setSy(value)
    
    @property
    def length(self):
        return self._node.getSz()
    @length.setter
    def length(self, value):
        self._node.setSz(value)
    
    def _load(self, Frame):
        self._node = shapeGenerator.Cylinder(1,1)
        self._node.reparentTo(Frame)

class cone(_Renderable):
    def __init__(self, **kwargs):
        # For some reason visual allows the length to be set with the axis vector
        # Attempt to duplicate this behavior by using the axis vector magnitude as the
        # default for the length parameter.
        #length = Vec3(kwargs.get('axis', Vec3(1,0,0))).mag

        # Call the base class
        _Renderable.__init__(self, **kwargs)
        
        # Set up the properties unique to this shape -- use pop so
        # we consume the keys so the base class does not get extras
        self.radius = kwargs.pop('radius', 1)
        self.radius2 = kwargs.pop('radius2', 0) # This is an added property vs the visual API
        
        #self.length = kwargs.pop('length', length)
        self.length = kwargs.pop('length', 1)

        # If there are any keys left in kwargs then raise an error
        if kwargs:
            #raise TypeError, "Unexpected keyword argument(s): " + str(kwargs.keys())
            pass # This is not working right, super clases are getting copies of kwargs, not references
                
        
    @property
    def radius(self):
        return self._node.getSx()
    @radius.setter
    def radius(self, value):
        self._node.setSx(value)
        self._node.setSy(value)
    
    @property
    def length(self):
        return self._node.getSz()
    @length.setter
    def length(self, value):
        self._node.setSz(value)
    
    def _load(self, Frame):
        self._node = shapeGenerator.Cone(1,1)
        self._node.reparentTo(Frame)

class ring(_Renderable):
    def __init__(self, **kwargs):
        # Call the base class
        _Renderable.__init__(self, **kwargs)
        
        # Set up the properties unique to this shape -- use pop so
        # we consume the keys so the base class does not get extras
        self.radius = kwargs.pop('radius', 1)
        
        self.thickness = kwargs.pop('thickness', self.radius / 10.0)

        # If there are any keys left in kwargs then raise an error
        if kwargs:
            #raise TypeError, "Unexpected keyword argument(s): " + str(kwargs.keys())
            pass # This is not working right, super clases are getting copies of kwargs, not references
                
        
    @property
    def radius(self):
        return self._node.getSx() / 2.0
    @radius.setter
    def radius(self, value):
        self._node.setSx(value * 2.0)
        self._node.setSy(value * 2.0)

    @property
    def thickness(self):
        return self._node.getSz()
    @thickness.setter
    def thickness(self, value):
        # Have to kludge since the thickness in the model is fixed
        self._node.setSz(self.radius * 2)
    
    def _load(self, Frame):
        assert False, 'Ring shape is not yet implemented'
        self._node = _LOAD('torus.egg')
        self._node.reparentTo(Frame)

class ellipsoid(_Renderable):
    def __init__(self, **kwargs):
        # Call the base class
        _Renderable.__init__(self, **kwargs)

        # Set up the properties unique to this shape -- use pop so
        # we consume the keys so the base class does not get extras
        self.size = kwargs.pop('size', (1,1,1)) # This must be before l,h,w
        if 'length' in kwargs: self.length = kwargs.pop('length') # x-axis
        if 'height' in kwargs: self.height = kwargs.pop('height') # y-axis
        if 'width' in kwargs: self.width = kwargs.pop('width') # z-axis
        
        # If there are any keys left in kwargs then raise an error
        if kwargs:
            #raise TypeError, "Unexpected keyword argument(s): " + str(kwargs.keys())
            pass # This is not working right, super clases are getting copies of kwargs, not references
        
    @property
    def size(self):
        return self._node.getScale()
    @size.setter
    def size(self, value):
        self._node.setScale(value)
    
    @property
    def length(self):
        return self._node.getSx()
    @length.setter
    def length(self, value):
        self._node.setSx(value)
    
    @property
    def height(self):
        return self._node.getSy()
    @height.setter
    def height(self, value):
        self._node.setSy(value)
    
    @property
    def width(self):
        return self._node.getSz()
    @width.setter
    def width(self, value):
        self._node.setSz(value)
    
    def _load(self, Frame):
        self._node = shapeGenerator.Sphere(1)
        self._node.reparentTo(Frame)
            
class arrow(_Renderable):
    def __init__(self, **kwargs):
        # For some reason visual allows the length to be set with the axis vector
        # Attempt to duplicate this behavior by using the axis vector magnitude as the
        # default for the length parameter.
        #length = Vec3(kwargs.get('axis', Vec3(1,0,0))).mag

        # Call the base class
        _Renderable.__init__(self, **kwargs)
        
        # Set up the properties unique to this shape -- use pop so
        # we consume the keys so the base class does not get extras
        #self.length = kwargs.pop('length', length)
        self.length = kwargs.pop('length', 1)
        self.fixedwidth = kwargs.pop('fixedwidth', False)
        self.shaftwidth = kwargs.pop('shaftwidth', 0.1 * self.length)
        self.headwidth = kwargs.pop('headwidth', 2 * self.shaftwidth)
        self.headlength = kwargs.pop('headlength', 3 * self.shaftwidth)

        # If there are any keys left in kwargs then raise an error
        if kwargs:
            #raise TypeError, "Unexpected keyword argument(s): " + str(kwargs.keys())
            pass # This is not working right, super clases are getting copies of kwargs, not references
                
        
    @property
    def length(self):
        return self._node.getSz()
    @length.setter
    def length(self, value):
        self._node.setScale(float(value))

    @property # TODO: Need to implement this
    def fixedwidth(self):
        return self._fixedwidth
    @fixedwidth.setter
    def fixedwidth(self, value):
        self._fixedwidth = bool(value)
    
    @property
    def shaftwidth(self):
        return self._shaft.getSx() * self.length
    @shaftwidth.setter
    def shaftwidth(self, value):
        if value < 1e-15: # Match behavior of visual
            value = 0.1 * self.length

        self._shaft.setSx(value / self.length)
        self._shaft.setSy(value / self.length)
        
    @property
    def headwidth(self):
        return self._head.getSx() * self.length
    @headwidth.setter
    def headwidth(self, value):
        self._head.setSx(value / self.length)
        self._head.setSy(value / self.length)
    
    @property
    def headlength(self):
        return self._head.getSz() * self.length
    @headlength.setter
    def headlength(self, value):
        headLength = value / self.length
        self._head.setSz(headLength)
        shaftLength = 1.0 - headLength
        self._shaft.setSz(shaftLength)
        
        self._shaft.setZ(shaftLength / 2.0)
        self._head.setZ(shaftLength + headLength / 2.0)
        
    def _load(self, Frame):
        _Renderable._load(self, Frame)
        
        self._shaft = shapeGenerator.Cylinder(0.5,1)
        self._shaft.reparentTo(self._node)

        self._head = shapeGenerator.Cone(0.5,1)
        self._head.reparentTo(self._node)

class faces(_RenderableArray):
    def __init__(self, **kwargs):
        self._normals = []
        # Call the base class
        _RenderableArray.__init__(self, **kwargs)
        
        # Set up the properties using passed parameters or defaults
        if 'normal' in kwargs: self._verticies = kwargs.pop('normal')

        # If there are any keys left in kwargs then raise an error
        if kwargs:
            #raise TypeError, "Unexpected keyword argument(s): " + str(kwargs.keys())
            pass # This is not working right, super clases are getting copies of kwargs, not references
        
    @property
    def normal(self):
        # Make a copy for the user to use
        return [normal for normal in self._normals]

    @normal.setter
    def normal(self, value):
        # Make a copy of the user's iterable
        self._normals = [Vec3(normal) for normal in value]
        self._fixArrays(len(self._normals))

    def _fixArrays(self, length):
        # Call the base class
        _RenderableArray._fixArrays(self, length)
        
        # Fix our normals list
        l = len(self._normals)
        if l < length:
            try:
                default = self._normals[-1]
            except IndexError:
                default = None
            self._normals.extend([Vec3(default) if default is not None else None for i in range(length-l)])
        elif l > length:
            self._normals = self._normals[0:length]
    
    def append(self, kwargs):
        # Call the base class version
        _RenderableArray.append(self, **kwargs)
        
        normal = kwargs.pop('normal', None)
        self._normals.append(Vec3(normal) if normal else None)

    def make_twosided(self):
        pass
    
    def make_normals(self):
        self._normals = [None] * len(self._verticies)
        for index in range(0, len(self._verticies), 3):
            v1 = self._verticies[index + 1] - self._verticies[index]
            v2 = self._verticies[index + 2] - self._verticies[index + 1]
            normal = v1.cross(v2).normalize()
            self._normals[index] = normal
            self._normals[index + 1] = Vec3(normal) # Make a copy
            self._normals[index + 2] = Vec3(normal) # Make a copy
            
    def smooth(self):
        pass
        
    def _load(self, Frame):
        # TODO: Use shapeGenerator
        
        assert False, 'faces shape is not yet implemented'
        
class curve(_RenderableArray):
    def __init__(self, **kwargs):
        # Call the base class
        _RenderableArray.__init__(self, **kwargs)
        
        self.radius = kwargs.pop('radius', 0)
        
        # If there are any keys left in kwargs then raise an error
        if kwargs:
            #raise TypeError, "Unexpected keyword argument(s): " + str(kwargs.keys())
            pass # This is not working right, super clases are getting copies of kwargs, not references
        
    def _load(self, Frame):
        assert False, 'curve shape is not yet implemented'
        
import sys
if hasattr(sys,'frozen') and (sys.frozen=="windows_exe" or sys.frozen=="console_exe"):
    _fontPath="pvis\\resources\\"
else:
    import os
    _fontPath = os.path.split( __file__ )[0] + "/resources/"
    del os
del sys

class label(_Primitive, _Attributes): # Primitive property functions take precedence
    def __init__(self, **kwargs):
        # Call the base classes
        _Attributes.__init__(self, **kwargs) # Must come first
        _Primitive.__init__(self, **kwargs)
        
        self._fontNameRemap = {'Monospace':'Mono'}
        
        # Set up the properties using passed parameters or defaults
        self.pos = kwargs.pop('pos', Vec3(0,0,0)) # This must come before x,y,z
        if 'x' in kwargs: self.x = kwargs.pop('x')
        if 'y' in kwargs: self.y = kwargs.pop('y')
        if 'z' in kwargs: self.z = kwargs.pop('z')

        self.xoffset = kwargs.pop('xoffset', 0)        
        self.yoffset = kwargs.pop('yoffset', 0)        

        self.text = kwargs.pop('text', '')
        self.font = kwargs.pop('font', 'sans')
        self.height = kwargs.pop('height', 13)
        
        self.background = kwargs.pop('background', self.frame.background)
        self.border = kwargs.pop('border', 5)
        self.box = kwargs.pop('box', True)
        self.line = kwargs.pop('line', True)
        self.linecolor = kwargs.pop('linecolor', Color.gray(1))
        self.space = kwargs.pop('space', 0)
        
        if 'alpha' not in kwargs:
            self.alpha = 0.66 # This default is different than for other primitives
        
    @property
    def pos(self):
        return self._pos
    @pos.setter
    def pos(self, value):
        self._pos = Vec3(value)
    
    @property
    def x(self):
        return self._pos.x
    @x.setter
    def x(self, value):
        self._pos.x = value
    
    @property
    def y(self):
        return self._pos.y
    @y.setter
    def y(self, value):
        self._pos.y = value
    
    @property
    def z(self):
        return self._pos.z
    @z.setter
    def z(self, value):
        self._pos.z = value

    @property
    def xoffset(self):
        return self._xoffset
    @xoffset.setter
    def xoffset(self, value):
        self._xoffset = value
    
    @property
    def yoffset(self):
        return self._yoffset
    @yoffset.setter
    def yoffset(self, value):
        self._yoffset = value
    
    @property
    def text(self):
        return self._text
    @text.setter
    def text(self, value):
        self._text = value

    @property
    def font(self):
        return self._font
    @font.setter
    def font(self, value):
        self._font = value
        self._mapFont()

    @property
    def height(self):
        return self._height
    @height.setter
    def height(self, value):
        self._height = value
        self._mapFont()
        
    @property
    def background(self):
        return self._background
    @background.setter
    def background(self, value):
        self._background = value
    
    @property
    def border(self):
        return self._border
    @border.setter
    def border(self, value):
        self._border = value

    @property
    def box(self):
        return self._box
    @box.setter
    def box(self, value):
        self._box = value
    
    @property
    def line(self):
        return self._line
    @line.setter
    def line(self, value):
        self._line = value

    @property
    def linecolor(self):
        return self._linecolor
    @linecolor.setter
    def linecolor(self, value):
        self._linecolor = VBase3(value)

    @property
    def space(self):
        return self._space
    @space.setter
    def space(self, value):
        self._space = value
 
    def _mapFont(self):
        # Do our best attempt at mapping what was requested
        name = self.font.title()
        # Remap known variations into our desired names
        name = self._fontNameRemap.get(name, name)
        
        if name not in ('Mono', 'Sans', 'Serif'):
            name = 'Mono'
        
        try:
            if self.height <= 10.0:
                fontSize = 'sml'
            elif self.height <= 14:
                fontSize = 'med'
            else:
                fontSize = 'lrg'
        except AttributeError:
            fontSize = 'sml' 
        
        self._mappedFont = self._fontMap.get((name, fontSize), GLUT.GLUT_BITMAP_8_BY_13)

    def _load(self, Frame):
        pass

class text(_Renderable):
    # http://ftgl.sourceforge.net/docs/html/ftgl-tutorial.html
    def __init__(self, **kwargs):
        # Call the base classes
        _Renderable.__init__(self, **kwargs)
        
        self._fontNameRemap = {'Monospace':'Mono'}

        self.align = kwargs.pop('align', 'left')
        
        self.text = kwargs.pop('text', '')
        self.font = kwargs.pop('font', 'sans')
        self.height = kwargs.pop('height', 1)
        self.depth = kwargs.pop('depth', 0.2)
        
    @property
    def align(self):
        return self._align
    @align.setter
    def align(self, value):
        if value not in ('left', 'center', 'right'):
            raise TypeError, "align value must be one of the following: 'left', 'center', 'right'"
        self._align = value

    @property
    def text(self):
        return self._text
    @text.setter
    def text(self, value):
        self._text = value

    @property
    def font(self):
        return self._font
    @font.setter
    def font(self, value):
        self._font = value
        self._mapFont()

    @property
    def height(self):
        return self._height
    @height.setter
    def height(self, value):
        self._height = value
        self._mappedFont.FaceSize(int(self._height*72 + 0.5))
            
    @property
    def depth(self):
        return self._depth
    @depth.setter
    def depth(self, value):
        self._depth = value
        self._mappedFont.Depth(self._depth * 72.0) 

    @property
    def width(self):
        return 0
    
    @property
    def widths(self):
        return []
    
    def _mapFont(self):
        import FTGL # I don't like having this dependency but it handles everything
        
        # Need to do some nifty font selection based on self.font
        # For now just punt and always use this one
        name = self.font.title()
        # Remap known variations into our desired names
        name = self._fontNameRemap.get(name, name)
        
        if name not in ('Mono', 'Sans', 'Serif'):
            name = 'Sans'
            
        name = 'Free%s.ttf' % name 
        self._mappedFont = FTGL.ExtrdFont(_fontPath + name)
        try:
            self._mappedFont.FaceSize(int(self._height*72 + 0.5))
        except AttributeError:
            pass 

        try:
            self._mappedFont.Depth(self._depth * 72.0)
        except AttributeError:
            pass 
    
    def _load(self, Frame):
        pass


    

    


    


    

    


    
